﻿#region Librerias

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using iTextSharp.text;
using iTextSharp.text.pdf;
using ESF.General;
using ESF.Data;
using System.Data;
using System.Threading;
using System.Threading.Tasks;
using System.Reflection;
using System.Windows.Media.Imaging;

#endregion


namespace ESF.FileProcessing
{
	public class FileSizeOptimizer : BaseTaskManager
	{
		#region Members

		private string _ProcessName;

		private DbReader R;

		private DbWriter W;

		//private DateTime _ProcessDateTimeLimit;

		private int _Width;
		private int _Height; 
		private bool _ResizeImages;
		private string _TmpWorkFolder;
		private bool _UseTitle;
		private string _Title;
		private bool _UseAuthor;
		private string _Author;

		#endregion


		#region DB Functions

		public bool IsAlreadyProcessed (FileInfo pFile)
		{
			IDbCommand cmd;

			DataRow data;

			
			// retorna los procesados del mismo nombre
			cmd = R.CreateCommandWithoutTimeout ("SELECT ID, LastWriteDateTime FROM FileProcessLogger WHERE FileName = @pFileName WHERE ProcessName = @pProcessName");// AND ProcessedDateTime >= @pProcessDateTimeLimit");
	
			//R.AddParameter (cmd, "pProcessDateTimeLimit", _ProcessDateTimeLimit);
			R.AddParameter (cmd, "pProcessName", _ProcessName);
			R.AddParameter (cmd, "pFileName", pFile.FullName);

			data = R.GetFirstRow (cmd);

			if (DataHelper.IsEmpty (data)) { return false; }

			/*// Parece que no almacena detalles micro asi se garantiza evitar caer en una repeticion innecesaria
			if (((TimeSpan) (pFile.LastWriteTime - Convert.ToDateTime (data ["LastWriteDateTime"]))).TotalMilliseconds > 1000)
			{
				return false;
			}*/

			return true;
		}


		public void RegisterProgress (FileInfo pItem, int pPageCount, long pProcessingTimeMs, Exception pException)
		{
			IDbCommand cmd;

			long? ID;

			
			ID = RegisterBasic (pItem);

			cmd = W.CreateCommandWithoutTimeout ("UPDATE FileProcessLogger SET Length = @pLength, PageCount = @pPageCount, ProcessedDateTime = @pProcessedDateTime, LastWriteDateTime = @pLastWriteDateTime, ProcessingTimeMs = @pProcessingTimeMs, Exception = @pException WHERE FileName = @pFileName AND ProcessName = @pProcessName");

			W.AddIDParameter (cmd, "pID", ID);
			
			R.AddParameter (cmd, "pProcessedDateTime", DateTime.Now);
			W.AddParameter (cmd, "pProcessName", _ProcessName);
			W.AddParameter (cmd, "pFileName", pItem.FullName);
			W.AddParameter (cmd, "pLength", pItem.Length);
			W.AddParameter (cmd, "pPageCount", pPageCount);
			W.AddParameter (cmd, "pLastWriteDateTime", pItem.LastWriteTime);
			W.AddParameter (cmd, "pProcessingTimeMs", pProcessingTimeMs);
			W.AddParameter (cmd, "pException", pException == null ? null : pException.Message);			

			W.Execute (cmd);
		}
		
		
		public long RegisterBasic (FileInfo pItem)
		{
			IDbCommand cmd;

			long? ID;

			
			cmd = R.CreateCommandWithoutTimeout ("SELECT ID FROM FileProcessLogger WHERE FileName = @pFileName AND ProcessName = @pProcessName");
	
			W.AddParameter (cmd, "pProcessName", _ProcessName);
			W.AddParameter (cmd, "pFileName", pItem.FullName);

			ID = R.GetScalar<int?> (cmd);


			
			if (ID == null)
			{
				cmd = W.CreateCommandWithoutTimeout ("INSERT INTO FileProcessLogger (FileName, Length, LastWriteDateTime, ProcessName) " + 
					"VALUES (@pFileName, @pLength, @pLastWriteDateTime, @pProcessName)");
			}
			else
			{
				cmd = W.CreateCommandWithoutTimeout ("UPDATE FileProcessLogger SET FileName = @pFileName, Length = @pLength, LastWriteDateTime = @pLastWriteDateTime WHERE ID = @pID AND ProcessName = @pProcessName");

				W.AddParameter (cmd, "@pID", ID);
			}

			W.AddParameter (cmd, "pProcessName", _ProcessName);
			W.AddParameter (cmd, "pFileName", pItem.FullName);
			W.AddParameter (cmd, "pLength", pItem.Length);
			W.AddParameter (cmd, "pLastWriteDateTime", pItem.LastWriteTime);
			
			W.Execute (cmd);

			cmd = R.CreateCommandWithoutTimeout ("SELECT ID FROM FileProcessLogger WHERE FileName = @pFileName AND ProcessName = @pProcessName");
	
			W.AddParameter (cmd, "pProcessName", _ProcessName);
			W.AddParameter (cmd, "pFileName", pItem.FullName);

			return R.GetScalar<int> (cmd);
		} 


		public void DeleteResults ()
		{
			IDbCommand cmd;


			cmd = W.CreateCommandWithoutTimeout ("DELETE FROM FileProcessLogger WHERE ProcessName = @pProcessName");

			W.AddParameter (cmd, "pProcessName", _ProcessName);

			W.Execute (cmd);
		}


		public int GetTotalAdvance ()
		{
			IDbCommand cmd;


			cmd = R.CreateCommandWithoutTimeout ("SELECT COUNT (ID) FROM FileProcessLogger WHERE ProcessingTimeMs IS NOT NULL AND ProcessedDateTime IS NOT NULL AND ProcessName = @pProcessName");

			W.AddParameter (cmd, "pProcessName", _ProcessName);

			return R.GetScalar<int> (cmd);
		}


		public int GetTotalFileCount ()
		{
			IDbCommand cmd;


			cmd = R.CreateCommandWithoutTimeout ("SELECT COUNT (ID) FROM FileProcessLogger WHERE ProcessName = @pProcessName");

			W.AddParameter (cmd, "pProcessName", _ProcessName);

			return R.GetScalar<int> (cmd);
		}


		public DataTable GetFiles ()
		{
			IDbCommand cmd;


			cmd = R.CreateCommandWithoutTimeout ("SELECT * FROM FileProcessLogger WHERE ProcessName = @pProcessName ORDER BY FileName");

			W.AddParameter (cmd, "pProcessName", _ProcessName);

			return R.GetFirstDataTable (cmd);
		}


		public IEnumerable<FileInfo> GetFilesPending ()
		{
			IDbCommand cmd;


			cmd = R.CreateCommandWithoutTimeout ("SELECT FileName FROM FileProcessLogger WHERE ProcessName = @pProcessName AND ProcessingTimeMs IS NULL AND ProcessedDateTime IS NULL ORDER BY FileName");

			W.AddParameter (cmd, "pProcessName", _ProcessName);

			return R.GetReader<FileInfo> (cmd, delegate (IDataReader pReader, string pPrefix, string pListLoading) { return new FileInfo (DataHelper.GetString (pReader, "FileName")); });
		}

		#endregion


		#region Constructor

		public FileSizeOptimizer (ProcessHelper.ReportProgressDelegate pdelReportProgressDelegate = null)
			: base (pdelReportProgressDelegate)
		{
			R = new DbReader("DB", "20");

			W = new DbWriter("DB", "20");
		}

		#endregion


		#region Funciones
		
		public void Initialize (int pWidth, int pHeight, 
			bool pResizeImages, string pTmpWorkFolder,
			bool pUseTitle, string pTitle,
			bool pUseAuthor, string pAuthor)
		{
			 _Width = pWidth;
			_Height = pHeight; 
			_ResizeImages = pResizeImages;
			_TmpWorkFolder = pTmpWorkFolder;
			_UseTitle = pUseTitle;
			_Title = pTitle;
			_UseAuthor = pUseAuthor;
			_Author = pAuthor;
		}


		public void OptimizeFiles
		(
			string pProcessName,
			bool pForgetAdvance,
			bool pVerifyFileList, string pFolderPath,
			string [] pValidExtensions
		)
		{
			_ProcessName = pProcessName;

			try
			{
				if (pForgetAdvance)
				{
					ReportProgress ("Borrando avance");

					DeleteResults();
				}

				if (pVerifyFileList)
				{
					ReportProgress ("Verificando el listado de archivos");

					throw new Exception ("Must be reviewed by developer");

					ESF.General.FileHelper.ExecuteInDirectoryFiles (
						pFolderPath,
						delegate(FileInfo pFile, object[] pParams)
						{
							RegisterBasic(pFile);
						},
						pFileExtensions: pValidExtensions,
						pCTS: CancelTokenSource);
				}

				Start (GetTotalFileCount ());

				InitializeReadyCount (GetTotalAdvance());

				Parallel.ForEach<FileInfo> (
					GetFilesPending(),
					ProcessHelper.GetDefaultParallelOptions (CancelTokenSource),
					OptimizeFile);
			}
			catch (OperationCanceledException cex)
			{
				Log.Error(cex);
			}
			finally
			{
				Stop();
			}
		}

		
		private void OptimizeFile (FileInfo pFile)
		{
			string tempDir = null;

			Stopwatch Lsw = new Stopwatch ();

			Document pdfNew;

			PdfWriter pdfWriter;

			PdfReader pdfOld;

			iTextSharp.text.Image imgTemp;

			PdfDictionary outi;

			Dictionary<String, String> info = new Dictionary<string, string>();

			string tempNewFile;

			int pageCount = 0;

			Lsw.Start ();

		
			try
			{	
				switch (pFile.Extension.ToUpper())
				{
					#region PDF
				
					case ".PDF":

						#region Extraccion

						tempDir = Path.Combine(_TmpWorkFolder, "Temp_" + Guid.NewGuid().ToString());

						ESF.General.FileHelper.DeleteDirectory(tempDir);

						ESF.General.FileHelper.EnsureFolder(tempDir);

						PDFHelper.ExtractImagesFromPDF(pFile.FullName, tempDir);

						pdfOld = new PdfReader(pFile.FullName);

						if (_UseTitle)
						{
							info["Title"] = _Title;
						}
						else if (pdfOld.Info.ContainsKey("Title"))
						{
							info["Title"] = pdfOld.Info["Title"];
						}
						else
						{
							info["Title"] = string.Empty;
						}

						if (_UseAuthor)
						{
							info["Author"] = _Author;
						}
						else if (pdfOld.Info.ContainsKey("Author"))
						{
							info["Author"] = pdfOld.Info["Author"];
						}
						else
						{
							info["Author"] = "ESF";
						}

						info["ProcessDate"] = DateTime.Now.Ticks.ToString();

						#endregion

						#region Creacion

						pdfNew = new Document(pdfOld.GetPageSize(1), 0, 0, 0, 0);

						tempNewFile = Path.Combine(pFile.Directory.ToString(), Guid.NewGuid().ToString() + ".tmp");

						pdfWriter = PdfWriter.GetInstance(pdfNew, File.Create(tempNewFile));

						pdfWriter.PDFXConformance = PdfWriter.PDFA1B;

						pdfNew.Open();

						pdfWriter.PdfVersion = PdfWriter.VERSION_1_7;
						pdfWriter.SetFullCompression();

						pdfNew.AddKeywords("Digitalizado por " + ReadConfig.PublisherName);
						pdfNew.AddAuthor(info["Author"]);
						pdfNew.AddTitle(info["Title"]);
						pdfNew.AddSubject("Digitalización");
						pdfNew.AddCreationDate();

						#region PDF1A

						outi = new PdfDictionary(PdfName.OUTPUTINTENT);

						outi.Put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString("sRGB IEC61966-2.1"));
						outi.Put(PdfName.INFO, new PdfString("sRGB IEC61966-2.1"));
						outi.Put(PdfName.S, PdfName.GTS_PDFA1);

						ICC_Profile icc = ICC_Profile.GetInstance(Assembly.GetExecutingAssembly().GetManifestResourceStream("ESF.FileHelper.ICC.sRGB_IEC61966-2-1_no_black_scaling.icc"));
						PdfICCBased ib = new PdfICCBased(icc);
						ib.Remove(PdfName.ALTERNATE);
						outi.Put(PdfName.DESTOUTPUTPROFILE, pdfWriter.AddToBody(ib).IndirectReference);

						pdfWriter.ExtraCatalog.Put(PdfName.OUTPUTINTENTS, new PdfArray(outi));

						pdfWriter.CreateXmpMetadata();

						#endregion


						foreach (FileInfo imgFile in new DirectoryInfo(tempDir).GetFiles("*.jpg").OrderBy(f => int.Parse(f.Name.Replace(f.Extension, string.Empty))))
						{
							if (_ResizeImages)
							{
								ImageHelper.GenerateTumbnail(imgFile.FullName, imgFile.FullName, _Width, _Height);

								//ImageHelper.Compress (imgFile.FullName, imgFile.FullName, 85);
							}

							using (FileStream imgStream = ESF.General.FileHelper.GetFileStream(imgFile.FullName))
							{
								imgTemp = iTextSharp.text.Image.GetInstance(imgStream);

								imgTemp.ScaleToFit(pdfOld.GetPageSize(1).Width, pdfOld.GetPageSize(1).Height);

								pdfNew.Add(imgTemp);

								pageCount++;
							}
						}

						pdfNew.Close();

						pdfWriter.Close();

						ESF.General.FileHelper.DeleteDirectory(tempDir);

						Monitor.Enter(pFile);

						ESF.General.FileHelper.Delete(pFile.FullName);

						File.Move(tempNewFile, pFile.FullName);

						Monitor.Exit(pFile);

						#endregion

						break;

					#endregion																																															#region Tiff

					#region Tiff

					case ".TIF":

						BmpBitmapEncoder encoder;

						string tmpBaseFileName = Path.Combine(_TmpWorkFolder, "Temp_" + Guid.NewGuid().ToString());

						ESF.General.FileHelper.EnsureFolder(tmpBaseFileName);


						using (Stream imageStreamSource = ESF.General.FileHelper.GetFileStream(pFile.FullName))
						{
							TiffBitmapDecoder decoder = new TiffBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);

							for (int imageIndex = 0; imageIndex < decoder.Frames.Count; imageIndex++)
							{
								encoder = new BmpBitmapEncoder();

								encoder.Frames.Add(decoder.Frames[imageIndex]);

								string tmpName = Path.Combine(tmpBaseFileName, imageIndex + ".jpg");

								using (FileStream file = new FileStream(tmpName, FileMode.Create))
								{
									encoder.Save(file);
								}

								if (_ResizeImages)
								{
									ImageHelper.GenerateTumbnail(tmpName, tmpName, _Width, _Height);

									//ImageHelper.Compress (tmpName, tmpName, 85);
								}
							}
						}


						List<FileStream> readStreams = new List<FileStream>();

						// ya se tiene los jpg listos para volver a armar el nuevo tiff
						BitmapPalette myPalette = BitmapPalettes.WebPalette;
						TiffBitmapEncoder encoderTif = new TiffBitmapEncoder();
						encoderTif.Compression = TiffCompressOption.Zip;

						string newTiffFileName = Path.Combine(pFile.Directory.FullName, Guid.NewGuid().ToString() + ".tmp");

						using (FileStream newTiff = new FileStream(newTiffFileName, FileMode.Create))
						{
							foreach (FileInfo item in new DirectoryInfo(tmpBaseFileName).GetFiles("*.jpg").OrderBy(f => int.Parse(f.Name.Replace(f.Extension, string.Empty))))
							{
								readStreams.Add(ESF.General.FileHelper.GetFileStream(item.FullName));

								BitmapDecoder d = BitmapDecoder.Create(readStreams.Last(), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);

								encoderTif.Frames.Add(d.Frames.First());
							}

							encoderTif.Save(newTiff);

							foreach (FileStream r in readStreams) { r.Close(); }

							readStreams.Clear();
						}

						ESF.General.FileHelper.DeleteDirectory(tmpBaseFileName);

						ESF.General.FileHelper.Delete(pFile.FullName);

						File.Move(newTiffFileName, pFile.FullName);

						break;

					#endregion

					#region Default

					default:

						pageCount = 1;

						if (_ResizeImages)
						{
							ImageHelper.GenerateTumbnail(pFile.FullName, pFile.FullName, _Width, _Height);
						}

						//ImageHelper.Compress (pFile.FullName, pFile.FullName, (int) nupJPGCompressionLevel.Value);

						break;

					#endregion
				}

				IncrementReadyCount ();
			}
			catch (Exception pEx)
			{
				pEx.Data.Add ("File", pFile.FullName);

				Log.Error (pEx);

				IncrementErrorCount(pEx);
			}
			finally
			{
				ReportProgress ();

				Lsw.Stop();
			}

			RegisterProgress (pFile, pageCount, Lsw.ElapsedMilliseconds, null);
		}

		#endregion
	}
}
